#include "hash.h"

Hash::Hash(HashType type) {
    ctx = EVP_MD_CTX_new();
    if (!ctx) throw std::runtime_error("Failed to create EVP_MD_CTX");

    const EVP_MD* md = _GetDigest(type);
    if (!EVP_DigestInit_ex(ctx, md, nullptr))
        throw std::runtime_error("DigestInit failed");
}

Hash::~Hash() {
    if (ctx) EVP_MD_CTX_free(ctx);
}

int Hash::Update(const void* data, size_t len) {
    if (!EVP_DigestUpdate(ctx, data, len))
    {
        std::cerr << "DigestUpdate failed" << std::endl;
        return -1;
    }
    return 0;
}

std::string Hash::Final() {
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int len = 0;

    if (!EVP_DigestFinal_ex(ctx, hash, &len))
    {
        return "";
    }
    return ToHex(hash, len);
}

std::string Hash::GetHash(HashType type, const unsigned char* data, size_t len) {
    EVP_MD_CTX* tmp_ctx = EVP_MD_CTX_new();
    if (!tmp_ctx) throw std::runtime_error("Failed to create EVP_MD_CTX");

    const EVP_MD* md = _GetDigest(type);
    if (!EVP_DigestInit_ex(tmp_ctx, md, nullptr))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }
    if (!EVP_DigestUpdate(tmp_ctx, data, len))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }

    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hash_len = 0;
    if (!EVP_DigestFinal_ex(tmp_ctx, hash, &hash_len))
    {
        EVP_MD_CTX_free(tmp_ctx);
        return "";
    }

    EVP_MD_CTX_free(tmp_ctx);
    return ToHex(hash, hash_len);
}

std::string Hash::ToHex(const unsigned char* data, size_t len) {
    std::ostringstream oss;
    for (size_t i = 0; i < len; ++i)
        oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
    return oss.str();
}


const EVP_MD* Hash::_GetDigest(HashType type) {
    switch (type) {
        case SHA224_TYPE: return EVP_sha224();
        case SHA256_TYPE: return EVP_sha256();
        case SHA384_TYPE: return EVP_sha384();
        case SHA512_TYPE: return EVP_sha512();
        default: throw std::invalid_argument("Invalid hash type");
    }
}



// OldHash::OldHash(HashType type): m_type(type)
// {   
//     int ret = 0;
//     switch (type)
//     {
//         case SHA224_TYPE:
//             ret = SHA224_Init(&m_ctx256);
//             break;
//         case SHA256_TYPE:
//             ret = SHA256_Init(&m_ctx256);
//             break;
//         case SHA384_TYPE:
//             ret = SHA384_Init(&m_ctx512);
//             break;
//         case SHA512_TYPE:
//             ret = SHA512_Init(&m_ctx512);
//             break;
//         default:
//             throw std::invalid_argument("Invalid hash type");
//     }

//     if (ret != 1)
//     {
//         std::cout << "Hash init error, ret: " << ret << std::endl;
//         throw std::runtime_error("Failed to initialize hash context");
//     }
// }

// int OldHash::Update(const void* data, size_t len)
// {
//     int ret = 0;
//     switch (m_type)
//     {
//         case SHA224_TYPE:
//             ret = SHA224_Update(&m_ctx256, data, len);
//             break;
//         case SHA256_TYPE:
//             ret = SHA256_Update(&m_ctx256, data, len);
//             break;
//         case SHA384_TYPE:
//             ret = SHA384_Update(&m_ctx512, data, len);
//             break;
//         case SHA512_TYPE:
//             ret = SHA512_Update(&m_ctx512, data, len);
//             break;
//         default:
//             throw std::invalid_argument("Invalid hash type");
//     }

//     return ret;
// }

// std::string OldHash::Final()
// {
//     std::string res;
//     int ret = 0;
//     switch (m_type)
//     {
//         case SHA224_TYPE:
//             {
//                 unsigned char md[SHA224_DIGEST_LENGTH];
//                 ret = SHA224_Final(md, &m_ctx256);
//                 ToHex(res, md, SHA224_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA256_TYPE:
//             {
//                 unsigned char md[SHA256_DIGEST_LENGTH];
//                 ret = SHA256_Final(md, &m_ctx256);
//                 ToHex(res, md, SHA256_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA384_TYPE:
//             {
//                 unsigned char md[SHA384_DIGEST_LENGTH];
//                 ret = SHA384_Final(md, &m_ctx512);
//                 ToHex(res, md, SHA384_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA512_TYPE:
//             {
//                 unsigned char md[SHA512_DIGEST_LENGTH];
//                 ret = SHA512_Final(md, &m_ctx512);
//                 ToHex(res, md, SHA512_DIGEST_LENGTH);
//                 break;
//             }
//         default:
//             throw std::invalid_argument("Invalid hash type");
//     }


//     if (ret != 1)
//     {
//         std::cout << "final error, ret: " << ret << std::endl;
//         res.clear();
//     }
//     return res;
// }


// std::string OldHash::GetHash(const unsigned char *d, size_t n)
// {
//     std::string res;
//     switch (m_type)
//     {
//         case SHA224_TYPE:
//             {
//                 unsigned char md[SHA224_DIGEST_LENGTH];
//                 SHA224(d, n, md);
//                 ToHex(res, md, SHA224_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA256_TYPE:
//             {
//                 unsigned char md[SHA256_DIGEST_LENGTH];
//                 SHA256(d, n, md);
//                 ToHex(res, md, SHA256_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA384_TYPE:
//             {
//                 unsigned char md[SHA384_DIGEST_LENGTH];
//                 SHA384(d, n, md);
//                 ToHex(res, md, SHA384_DIGEST_LENGTH);
//                 break;
//             }
//         case SHA512_TYPE:
//             {
//                 unsigned char md[SHA512_DIGEST_LENGTH];
//                 SHA512(d, n, md);
//                 ToHex(res, md, SHA512_DIGEST_LENGTH);
//                 break;
//             }
//         default:
//             throw std::invalid_argument("Invalid hash type");
//     }
//     return res;
// }


// void OldHash::ToHex(std::string& res, const unsigned char* data, size_t len)
// {
//     std::ostringstream oss;
//     for (size_t i = 0; i < len; ++i) 
//     {
//         oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
//     }
//     res = std::move(oss.str());
// }